// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VisionManifest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Vision
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using Microsoft.Kinect;
    using Microsoft.Robotics.Numerics;
    using Microsoft.Robotics.Runtime;
    using Microsoft.Robotics.Vision;
    using Microsoft.Robotics.Vision.Cameras;
    using Microsoft.Robotics.Vision.Cameras.Kinect;
    using Microsoft.Robotics.Vision.Cameras.Webcam;
    using Microsoft.Robotics.Vision.Runtime;
    using Microsoft.Robotics.Vision.Runtime.Cameras;
    using Microsoft.Robotics.Vision.Runtime.Cameras.Kinect;
    using Microsoft.Robotics.Vision.Runtime.GroundTruthExtraction;
    using Microsoft.Robotics.Vision.Visualization;
    using Microsoft.Robotics.Visualization;

    /// <summary>
    /// Provides methods for creating manifests in a type-checked way.
    /// </summary>
    public static class VisionManifest
    {
        /// <summary>
        /// RGB Camera exposure level. This was set to -10, but due to recent windows update (10/15/2013) where -10 is too dark it was changed to -5.
        /// </summary>
        public const int RGBCameraExposureLevel = -5;

        /// <summary>
        /// Adds a kinect, and it's visualizer, to the manifest
        /// </summary>
        /// <param name="manifest">manifest to add the agent too</param>
        /// <param name="views">The view to add the UI too</param>
        /// <param name="depthImageFormat">format of the depth image</param>
        /// <param name="colorImageFormat">format of the color image</param>
        /// <param name="pose">Pose of the kinect depth camera, default is 0 rotation and translation</param>
        /// <param name="leftRightImages">Images produced are left to right (default is true)</param>
        /// <param name="kinectMessageLag">The lag of the kinect</param>
        /// <param name="maximumRange">Maximum range of kinect sensor, if not set then will take parameters provided by Kinect</param>
        /// <param name="noReadingValue">The no reading value, if not set then will take parameters provided by Kinect</param>
        /// <param name="tooFarValue">Too far value, if not set then will take parameters provided by Kinect</param>
        /// <param name="tooNearValue">Too near value, if not set then will take parameters provided by Kinect</param>
        /// <returns>A kinect agent</returns>
        public static KinectAgent AddKinect(
            Manifest manifest, 
            List<ViewDefinition> views, 
            DepthImageFormat depthImageFormat = DepthImageFormat.Resolution640x480Fps30,
            ColorImageFormat colorImageFormat = ColorImageFormat.RgbResolution640x480Fps30,
            Pose pose = new Pose(),
            bool leftRightImages = true,
            long kinectMessageLag = 0,
            short maximumRange = -1,
            short noReadingValue = -1,
            short tooFarValue = -1,
            short tooNearValue = -1)
        {
            KinectAgent kinect = new KinectAgent("KinectAgent", depthImageFormat, colorImageFormat, pose, leftRightImages, kinectMessageLag, maximumRange, noReadingValue, tooFarValue, tooNearValue);

            manifest.Agents.Add(kinect);

            VisionManifest.AddDepthView<KinectDepthCameraParameters>(manifest, views, kinect, 33);

            if (colorImageFormat != ColorImageFormat.InfraredResolution640x480Fps30)
            {
                VisionManifest.AddColorView<KinectColorCameraParameters>(manifest, views, kinect, 33);
            }
            else
            {
                VisionManifest.AddKinectIRView(manifest, views, kinect, 33);
            }

            return kinect;
        }

        /// <summary>
        /// Records data from the kinect
        /// </summary>
        /// <param name="manifest">The manifest</param>
        /// <param name="kinect">Producer of kinect images</param>
        public static void AddKinectRecorder(Manifest manifest, KinectAgent kinect)
        {
            DepthImageCompressionAgent<KinectDepthCameraParameters> compressedDepth = VisionManifest.AddDepthImageCompression<KinectDepthCameraParameters>(manifest, kinect);
            manifest.ActiveAgents.Add(compressedDepth.Name);

            FileRecorderProxy depthRecorder = new FileRecorderProxy(
                name: "KinectCompressedDepthAgent",
                filePath: null,
                messageTypes: new[] { typeof(CompressedCameraAgentMessage<KinectDepthCameraParameters>) },
                producer: compressedDepth.Name,
                enableArrayCompression: false);
            manifest.Agents.Add(depthRecorder);
            manifest.ActiveAgents.Add(depthRecorder.Name);

            RgbImageCompressionAgent<KinectColorCameraParameters> compressedColor = VisionManifest.AddRgbImageCompression<KinectColorCameraParameters>(manifest, kinect);
            manifest.ActiveAgents.Add(compressedColor.Name);

            FileRecorderProxy colorRecorder = new FileRecorderProxy(
               name: "KinectCompressedRgbAgent",
               filePath: null,
               messageTypes: new[] { typeof(CompressedCameraAgentMessage<KinectColorCameraParameters>) },
               producer: compressedColor.Name,
               enableArrayCompression: false);

            manifest.Agents.Add(colorRecorder);
            manifest.ActiveAgents.Add(colorRecorder.Name);
        }

        /// <summary>
        /// Add a image compression manifest
        /// </summary>
        /// <typeparam name="TColor">The type of $rgb$ image</typeparam>
        /// <param name="manifest">The manifest</param>
        /// <param name="colorProducer">The producer of $rgb$ images</param>
        /// <returns>The $RgbImageCompressionAgent$</returns>
        public static RgbImageCompressionAgent<TColor> AddRgbImageCompression<TColor>(Manifest manifest, IProducer<ColorCameraAgentMessage<TColor>> colorProducer)
            where TColor : ColorParameters
        {
            RgbImageCompressionAgent<TColor> compressionAgent = new RgbImageCompressionAgent<TColor>(colorProducer.Name + "ColorCompressionAgent", colorProducer);

            manifest.Agents.Add(compressionAgent);

            return compressionAgent;
        }

        /// <summary>
        /// Add a image compression manifest
        /// </summary>
        /// <typeparam name="TDepth">The type of depth image</typeparam>
        /// <param name="manifest">The manifest</param>
        /// <param name="depthProducer">The producer of depth images</param>
        /// <returns>The DepthImageCompressionAgent</returns>
        public static DepthImageCompressionAgent<TDepth> AddDepthImageCompression<TDepth>(Manifest manifest, IProducer<DepthCameraAgentMessage<TDepth>> depthProducer)
            where TDepth : DepthParameters
        {
            DepthImageCompressionAgent<TDepth> compressionAgent = new DepthImageCompressionAgent<TDepth>(depthProducer.Name + "DepthCompressionAgent", depthProducer);

            manifest.Agents.Add(compressionAgent);

            return compressionAgent;
        }

        /// <summary>
        /// Adds a RGBDCameraAgent to the pipeline
        /// </summary>
        /// <typeparam name="TColorParameters">The parameters of the color camera</typeparam>
        /// <typeparam name="TDepthParameters">The parameters of the depth camera</typeparam>
        /// <param name="manifest">The manifest to add the agent too</param>
        /// <param name="views">The list of view definitions to add the visualizer too</param>
        /// <param name="colorProducer">The producer of color images</param>
        /// <param name="depthProducer">The producer of depth images</param>
        /// <param name="alignment">The alignment type</param>
        /// <returns>The RGBDCameraAgent</returns>
        public static RGBDCameraAgent<TColorParameters, TDepthParameters> AddRGBDCameraAgent<TColorParameters, TDepthParameters>(
            Manifest manifest,
            List<ViewDefinition> views,
            IProducer<ColorCameraAgentMessage<TColorParameters>> colorProducer,
            IProducer<DepthCameraAgentMessage<TDepthParameters>> depthProducer,
            Type alignment = null)
            where TColorParameters : ColorParameters
            where TDepthParameters : DepthParameters
        {
            RGBDCameraAgent<TColorParameters, TDepthParameters> rgbdAgent = new RGBDCameraAgent<TColorParameters, TDepthParameters>(
                "RGBDAgent",
                colorProducer,
                depthProducer,
                alignment);

            manifest.Agents.Add(rgbdAgent);

            RGBDCameraModelAgent<TColorParameters, TDepthParameters> rgbdModelAgent = new RGBDCameraModelAgent<TColorParameters, TDepthParameters>(
                "RGBDModelAgent",
                rgbdAgent,
                33);
            manifest.Agents.Add(rgbdModelAgent);

            ViewDefinition rgbdDef = new ViewDefinition("RGBD View", typeof(RGBDCameraView), rgbdModelAgent);
            views.Add(rgbdDef);

            return rgbdAgent;
        }

        /// <summary>
        /// Adds the raw EV4 kinect agent to the pipeline
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="imageFormat">Depth image format.</param>
        /// <param name="addview">Add depth view.</param>
        /// <returns>The kinect agent</returns>
        public static KinectAgent AddEV4Kinect(Manifest manifest, List<ViewDefinition> views, DepthImageFormat imageFormat = DepthImageFormat.Resolution640x480Fps30, bool addview = false)
        {
            KinectAgent kinect = new KinectAgent(
                name: "KinectAgentRaw",
                pose: new Pose(new Vector3(-0.059, 0, 0.539), new Quaternion(0, 0, 0, 1)),
                maximumRange: DepthImageConstants.MaximumRangeInMm,
                depthImageFormat: imageFormat,
                rgbImageFormat: ColorImageFormat.Undefined,
                noReadingValue: 0,
                tooNearValue: DepthImageConstants.TooClose,
                tooFarValue: DepthImageConstants.TooFar,
                kinectMessageLag: 0,
                leftRightImages: false);
            manifest.Agents.Add(kinect);

            if (addview)
            {
                VisionManifest.AddDepthView<KinectDepthCameraParameters>(manifest, views, kinect, 33, "EV4 Kinect Depth");
            }

            return kinect;
        }

        /// <summary>
        /// Adds a remoting proxy to broadcast raw Kinect messages over the network.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="kinect">The agent to network-enable.</param>
        /// <param name="targetNameOrAddress">The name or IP address of the machine to broadcast to (e.g. a desktop machine)</param>
        /// <returns>A remoting proxy</returns>
        public static ProxyDefinition AddKinectRawNetworkEnabledProxy(Manifest manifest, IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> kinect, string targetNameOrAddress)
        {
            ProxyDefinition kinectProxy = ProxyDefinition.CreateNetworkEnabledProxy(
                name: kinect.Name + "Proxy",
                producer: kinect.Name,
                address: targetNameOrAddress,
                publishContracts: new[] { typeof(DepthCameraAgentMessage<KinectDepthCameraParameters>) },
                publishNetworkPortNumber: (int)RemotingPorts.KinectRawPublish,
                controlContracts: null,
                controlNetworkPortNumber: 0);
            manifest.NetworkEnabledAgentProxies.Add(kinectProxy);

            return kinectProxy;
        }

        /// <summary>
        /// Adds a desktop proxy for a remote raw Kinect agent running on the robot.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="robotNameOrAddress">The name or IP address of the robot</param>
        /// <returns>The hdp proxy</returns>
        public static IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> AddKinectRawRemoteProxy(Manifest manifest, List<ViewDefinition> views, string robotNameOrAddress)
        {
            ProxyDefinition remoteProxy = ProxyDefinition.CreateRemoteProxy(
                name: "KinectRawAgent",
                address: robotNameOrAddress,
                publishContracts: new[] { typeof(DepthCameraAgentMessage<KinectDepthCameraParameters>) },
                publishNetworkPortNumber: (int)RemotingPorts.KinectRawPublish,
                controlContracts: null,
                controlNetworkPortNumber: 0);
            manifest.RemoteAgentProxies.Add(remoteProxy);

            return remoteProxy.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>();
        }

        /// <summary>
        /// Adds the EV4 kinect processor agent to the pipeline
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="kinect">The kinect agent generating raw data</param>
        /// <param name="robotConfigFolder">Calibration Data Folder</param>
        /// <param name="ceilingCutoffInMM">Ceiling cutoff in MM from the ground (optional)</param>
        /// <param name="disableFloorFiltering">Disables floor filtering</param>
        /// <param name="disableDepthMirror">Disables mirroring of depth image</param>
        /// <param name="disableAll">Disable all processing (pass through raw kinect data)</param>
        /// <returns>The kinect processor agent</returns>
        public static KinectAgentMessageProcessor AddKinectProcessor(
            Manifest manifest, 
            List<ViewDefinition> views, 
            IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> kinect,
            string robotConfigFolder,
            int ceilingCutoffInMM = 500, ////500 == 1 meter! The value is resolution dependent, and 1000 means 1m in 320x240 and 2m in 640x480
            bool disableFloorFiltering = false,
            bool disableDepthMirror = true, 
            bool disableAll = false)
        {
            KinectAgentMessageProcessor kinectProcessor = new KinectAgentMessageProcessor(
                name: "KinectProcessorAgent",
                producer: kinect,
                calibrationDataFolder: robotConfigFolder,
                disableDepthMirror: disableDepthMirror,
                maxAllowedHorizonDisplacement: 0.7,
                minAllowedHorizonDisplacement: 0.0,
                maxSurfaceRoughness: 300,
                maxVerticalTravelPercentFromCalibrated: 15,
                minVerticalTravelPercentFromCalibrated: 5,
                floorCutoffPlaneSlopeAggressiveness: 0.5,
                floorCutoffClearanceConstantShift: -20,
                floorCutoffClearanceLinearFunctionOfDistance: 0.065,
                ceilingCutoffInMM: ceilingCutoffInMM,
                disableFloorFiltering: disableFloorFiltering,
                disableAll: disableAll); 

            manifest.Agents.Add(kinectProcessor);

            VisionManifest.AddDepthView<KinectDepthCameraParameters>(manifest, views, kinectProcessor, 33, "Processed Depth View");

            return kinectProcessor;
        }

        /// <summary>
        /// Adds the EV4 kinect depth frame down-scaling agent
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="kinect">The kinect agent generating raw data</param>
        /// <param name="spacialResolutionScale">Spatial resolution downscale factor</param>
        /// <param name="frequencyScale">Temporal resolution downscale factor</param>
        /// <returns>The kinect processor agent</returns>
        public static KinectFrameDownScalingAgent AddKinectDownscalerAgent(
            Manifest manifest,
            IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> kinect,
            int spacialResolutionScale, 
            int frequencyScale)
        {
            KinectFrameDownScalingAgent agent = new KinectFrameDownScalingAgent(
                name: "DepthFrameDownScalingAgent",
                frequencyDownScaleFactor: frequencyScale,
                resolutionDownScaleFactor: spacialResolutionScale,
                producer: kinect);

            manifest.Agents.Add(agent);

            return agent;
        }

        /// <summary>
        /// Adds a remoting proxy to broadcast heartbeat agent messages over the network.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="transformer">The agent to network-enable.</param>
        /// <param name="desktopNameOrAddress">The name or IP address of the machine to broadcast to (e.g. a desktop machine)</param>
        /// <returns>A remoting proxy</returns>
        public static ProxyDefinition AddDepthFrameDownScalingNetworkEnabledProxy(Manifest manifest, IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> transformer, string desktopNameOrAddress)
        {
            ProxyDefinition transformerAgentProxy = ProxyDefinition.CreateNetworkEnabledProxy(
                name: transformer.Name + "Proxy",
                producer: transformer.Name,
                address: desktopNameOrAddress,
                publishContracts: new[] { typeof(DepthCameraAgentMessage<KinectDepthCameraParameters>) },
                publishNetworkPortNumber: (int)RemotingPorts.ScaledDownDepthPublish,
                controlContracts: null,
                controlNetworkPortNumber: 0);
            manifest.NetworkEnabledAgentProxies.Add(transformerAgentProxy);

            return transformerAgentProxy;
        }

        /// <summary>
        /// Adds proxy for scaled down kinect (debugging)
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="robotNameOrAddress">The name or IP address of the robot</param>
        /// <returns>down scaling agent</returns>
        public static IProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>> AddDownscaledDepthFrameProducerProxy(
            Manifest manifest, 
            List<ViewDefinition> views, 
            string robotNameOrAddress)
        {
            // now create a proxy with the same name and add it to the manifest
            ProxyDefinition remoteProxy = ProxyDefinition.CreateRemoteProxy(
                name: "KinectDownScaledDepthFrameAgent",
                address: robotNameOrAddress,
                publishContracts: new[] { typeof(DepthCameraAgentMessage<KinectDepthCameraParameters>) },
                publishNetworkPortNumber: (int)RemotingPorts.ScaledDownDepthPublish,
                controlContracts: null,
                controlNetworkPortNumber: 0);
            manifest.RemoteAgentProxies.Add(remoteProxy);

            return remoteProxy.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>();
        }

        /// <summary>
        /// Adds kinect depth camera view to the manifest
        /// </summary>
        /// <typeparam name="K">The camera parameter type</typeparam>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="depthProducer">The kinect agent generating raw data</param>
        /// <param name="maxUpdateIntervalMilliseconds">Max Update interval</param>
        /// <param name="viewName">The name of the view</param>
        public static void AddDepthView<K>(
            Manifest manifest,
            List<ViewDefinition> views,
            IProducer<DepthCameraAgentMessage<K>> depthProducer,
            int maxUpdateIntervalMilliseconds,
            string viewName = "Depth View") where K : DepthParameters
        {
            DepthCameraModelAgent<K> model = new DepthCameraModelAgent<K>(
                name: "DepthCameraModelAgent_" + depthProducer.Name,
                producer: depthProducer,
                maxUpdateIntervalMilliseconds: maxUpdateIntervalMilliseconds);

            manifest.Agents.Add(model);

            ViewDefinition def = new ViewDefinition(viewName, typeof(DepthCameraView), model);
            views.Add(def);
        }

        /// <summary>
        /// Adds color camera view to the manifest
        /// </summary>
        /// <typeparam name="K">The camera parameter type</typeparam>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="kinectProducer">The kinect agent generating raw data</param>
        /// <param name="maxUpdateIntervalMilliseconds">Max Update interval</param>
        /// <param name="viewName">The name of the view</param>
        public static void AddColorView<K>(
            Manifest manifest, 
            List<ViewDefinition> views, 
            IProducer<ColorCameraAgentMessage<K>> kinectProducer,
            int maxUpdateIntervalMilliseconds,
            string viewName = "Color View") where K : ColorParameters
        {
            ColorCameraModelAgent<K> model = new ColorCameraModelAgent<K>(
                name: "RGBCameraModelAgent",
                producer: kinectProducer,
                maxUpdateIntervalMilliseconds: maxUpdateIntervalMilliseconds);

            manifest.Agents.Add(model);

            ViewDefinition def = new ViewDefinition(viewName, typeof(ColorCameraView), model);
            views.Add(def);
        }

        /// <summary>
        /// Adds kinect infrared camera view to the manifest
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="kinectProducer">The kinect agent generating raw data</param>
        /// <param name="maxUpdateIntervalMilliseconds">Max Update interval</param>
        public static void AddKinectIRView(
            Manifest manifest, 
            List<ViewDefinition> views, 
            IProducer<IRCameraAgentMessage<KinectIRCameraParameters>> kinectProducer,
            int maxUpdateIntervalMilliseconds)
        {
            IRCameraModelAgent<KinectIRCameraParameters> model = new IRCameraModelAgent<KinectIRCameraParameters>(
                name: "IRCameraModelAgent",
                producer: kinectProducer,
                maxUpdateIntervalMilliseconds: maxUpdateIntervalMilliseconds);

            manifest.Agents.Add(model);

            ViewDefinition def = new ViewDefinition("Kinect IR", typeof(IRCameraView), model);
            views.Add(def);
        }

        /// <summary>
        /// Adds a playback proxy to the pipeline that replays the specified RGB recording.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="timer">The timer driving the playback</param>
        /// <param name="directory">The directory containing the file to play back</param>
        /// <param name="fileName">Optional. The name of the recorded file to play back.</param>
        /// <param name="addView">Optional. Adds a camera view if true.</param>
        /// <returns>The RGB Camera producer interface</returns>
        public static FilePlayerProxy AddRGBCameraPlayback(
            Manifest manifest,
            List<ViewDefinition> views,
            TimerAgent timer,
            string directory,
            string fileName = "RGBCamera.xml",
            bool addView = false)
        {
            FilePlayerProxy playbackProxy = new FilePlayerProxy(
                name: "RGBCamera",
                filePath: Path.Combine(directory, fileName),
                messageTypes: new[] { typeof(RGBCameraAgentMessage) },
                timerAgent: timer);
            manifest.Agents.Add(playbackProxy);

            if (addView)
            {
                WebcamRGBCameraModelAgent modelRGB = new WebcamRGBCameraModelAgent(
                name: "VisualCameraModelAgentRGB",
                producer: playbackProxy.AsProducer<RGBCameraAgentMessage>(),
                maxUpdateIntervalMilliseconds: 16,
                imageFormat: SimplePixelFormat.Rgb24);

                manifest.Agents.Add(modelRGB);

                ViewDefinition def = new ViewDefinition("Webcam View", typeof(WebCameraView), modelRGB);
                views.Add(def);
            }

            return playbackProxy;
        }

        /// <summary>
        /// Adds the file recorder proxy for RGB Camera Agent.
        /// </summary>
        /// <param name="manifest">The manifest to add the proxy to</param>
        /// <param name="rgbCamera">The RGB camera agent to record</param>
        /// <returns>The file recorder proxy</returns>
        public static FileRecorderProxy AddRGBCameraRecorder(Manifest manifest, IProducer<RGBCameraAgentMessage> rgbCamera)
        {
            FileRecorderProxy rgbCameraRecorder = new FileRecorderProxy(
                name: rgbCamera.Name + "Recorder",
                filePath: null,
                messageTypes: new[] { typeof(RGBCameraAgentMessage) },
                producer: rgbCamera.Name,
                enableArrayCompression: true);
            manifest.Agents.Add(rgbCameraRecorder);

            return rgbCameraRecorder;
        }

        /// <summary>
        /// Adds a depth playback to the manifest
        /// </summary>
        /// <typeparam name="TDepth">The type of depth parameters</typeparam>
        /// <param name="manifest">The manifest</param>
        /// <param name="timer">The timer</param>
        /// <param name="directory">The directory of the recording</param>
        /// <param name="filename">The filename of the recording</param>
        /// <returns>The decompressed depth image agent</returns>
        public static DepthImageDecompressionAgent<TDepth> AddDepthPlayback<TDepth>(Manifest manifest, TimerAgent timer, string directory, string filename) where TDepth : DepthParameters
        {
            FilePlayerProxy depth = new FilePlayerProxy(
                                                    name: "CompressedDepthPlaybackAgent",
                                                    filePath: Path.Combine(directory, filename),
                                                    messageTypes: new[] { typeof(CompressedCameraAgentMessage<TDepth>) },
                                                    timerAgent: timer);
            manifest.Agents.Add(depth);

            DepthImageDecompressionAgent<TDepth> depthDecompression = new DepthImageDecompressionAgent<TDepth>(
                "DepthDecompressionAgent",
                depth.AsProducer<CompressedCameraAgentMessage<TDepth>>());
            manifest.Agents.Add(depthDecompression);
            manifest.ActiveAgents.Add(depthDecompression.Name);

            return depthDecompression;
        }

        /// <summary>
        /// Adds a color playback to the manifest
        /// </summary>
        /// <typeparam name="TColor">The type of color parameters</typeparam>
        /// <param name="manifest">The manifest</param>
        /// <param name="timer">The timer</param>
        /// <param name="directory">The directory of the recording</param>
        /// <param name="filename">The filename of the recording</param>
        /// <returns>The decompressed depth image agent</returns>
        public static RgbImageDecompressionAgent<TColor> AddColorPlayback<TColor>(Manifest manifest, TimerAgent timer, string directory, string filename) where TColor : ColorParameters
        {
            FilePlayerProxy color = new FilePlayerProxy(
                                                    name: "CompressedColorPlaybackAgent",
                                                    filePath: Path.Combine(directory, filename),
                                                    messageTypes: new[] { typeof(CompressedCameraAgentMessage<TColor>) },
                                                    timerAgent: timer);
            manifest.Agents.Add(color);

            RgbImageDecompressionAgent<TColor> colorDecompression = new RgbImageDecompressionAgent<TColor>(
                "ColorDecompressionAgent",
                color.AsProducer<CompressedCameraAgentMessage<TColor>>());

            manifest.Agents.Add(colorDecompression);
            manifest.ActiveAgents.Add(colorDecompression.Name);

            return colorDecompression;
        }

        /// <summary>
        /// Creates a kinect test manifest
        /// </summary>
        /// <param name="record">record the stream</param>
        /// <returns>A manifest</returns>
        public static Manifest CreateKinectTestManifest(bool record)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            KinectAgent kinect = VisionManifest.AddKinect(manifest, views);

            RGBDCameraAgent<KinectColorCameraParameters, KinectDepthCameraParameters> rgbdAgent = VisionManifest.AddRGBDCameraAgent<KinectColorCameraParameters, KinectDepthCameraParameters>(
                manifest,
                views,
                kinect,
                kinect,
                typeof(KinectAlignment));

            manifest.ActiveAgents.Add(rgbdAgent.Name);

            if (record)
            {
                VisionManifest.AddKinectRecorder(manifest, kinect);
            }

            // add the UI
            UserInterfaceAgent interfaceAgent = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(interfaceAgent);
            manifest.ActiveAgents.Add(interfaceAgent.Name);

            return manifest;
        }

        /// <summary>
        /// Creates a manifest that plays back a Kinect recording.
        /// </summary>
        /// <param name="directory">The directory</param>
        /// <param name="depthFileName">The filename for the depth recording</param>
        /// <param name="colorFileName">The filename for the color recording</param>
        /// <returns>The new manifest</returns>
        public static Manifest CreateKinectPlaybackManifest(string directory, string depthFileName, string colorFileName)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            TimerAgent timer = RuntimeManifest.AddPlaybackTimer(manifest, views);
            if (colorFileName != null)
            {
                RgbImageDecompressionAgent<KinectColorCameraParameters> colorPlayback = VisionManifest.AddColorPlayback<KinectColorCameraParameters>(manifest, timer, directory, colorFileName);
                VisionManifest.AddColorView<KinectColorCameraParameters>(manifest, views, colorPlayback, 33);
            }

            if (depthFileName != null)
            {
                DepthImageDecompressionAgent<KinectDepthCameraParameters> depthPlayback = VisionManifest.AddDepthPlayback<KinectDepthCameraParameters>(manifest, timer, directory, depthFileName);
                VisionManifest.AddDepthView<KinectDepthCameraParameters>(manifest, views, depthPlayback, 33);
            }
           
            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);
            manifest.ActiveAgents.Add(userInterface.Name);

            return manifest;
        }

        /// <summary>
        /// Creates a manifest that converts the old Kinect recordings to the new framework
        /// </summary>
        /// <param name="directory">The directory of the recording</param>
        /// <param name="filename">The name of the file</param>
        /// <returns>A manifest</returns>
        public static Manifest CreateKinectConverter(string directory, string filename = "KinectAgent.xml")
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            // play the old file back
            TimerAgent timer = RuntimeManifest.AddPlaybackTimer(manifest, views);
            FilePlayerProxy kinectRecording = new FilePlayerProxy(
                "KinectFilePlayer",
                Path.Combine(directory, filename),
                new Type[] { typeof(KinectAgentRawMessage) },
                timer);

            manifest.Agents.Add(kinectRecording);

            // convert it
            KinectConverterAgent converter = new KinectConverterAgent("KinectConverter", kinectRecording.AsProducer<KinectAgentRawMessage>());
            manifest.Agents.Add(converter);

            // compress the depth image
            DepthImageCompressionAgent<KinectDepthCameraParameters> compressedDepth = VisionManifest.AddDepthImageCompression<KinectDepthCameraParameters>(manifest, converter);
            manifest.ActiveAgents.Add(compressedDepth.Name);

            // save it
            TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1);
            int secondsSinceEpoch = (int)t.TotalSeconds;
            FileRecorderProxy depthRecorder = new FileRecorderProxy(
                name: "KinectCompressedDepthRecorder",
                filePath: Path.Combine(directory, "ConvertedKinectAgent" + secondsSinceEpoch.ToString() + ".xml"),
                messageTypes: new[] { typeof(CompressedCameraAgentMessage<KinectDepthCameraParameters>) },
                producer: compressedDepth.Name,
                enableArrayCompression: false);
            manifest.Agents.Add(depthRecorder);
            manifest.ActiveAgents.Add(depthRecorder.Name);

            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);
            manifest.ActiveAgents.Add(userInterface.Name);

            return manifest;
        }

        /// <summary>
        /// Creates a manifest that plays back a RGB recording.
        /// </summary>
        /// <param name="filename">The name of the file to play</param>
        /// <param name="robotConfigFolder">Calibration Data Folder</param>
        /// <returns>The new manifest</returns>
        public static Manifest CreateRGBPlaybackManifest(string filename, string robotConfigFolder)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            TimerAgent timer = RuntimeManifest.AddPlaybackTimer(manifest, views);
            FilePlayerProxy rgbCamera = VisionManifest.AddRGBCameraPlayback(manifest, views, timer, string.Empty, filename);
            AddGenericRGBCameraView(manifest, rgbCamera.AsProducer<RGBCameraAgentMessage>());            

            manifest.ActiveAgents.Add(rgbCamera.Name);

            return manifest;
        }

        /// <summary>
        /// Create test manifest for web cam. Simply displays the web cam in an UI
        /// </summary>
        /// <param name="friendlyName">Web camera friendly name</param>
        /// <param name="width">Desired picture width</param>
        /// <param name="height">Desired picture height</param>
        /// <param name="frameRateNumerator">Frame Rate Numerator</param>
        /// <param name="frameRateDenominator">Frame Rate Denominator</param>
        /// <returns>The new manifest</returns>
        public static Manifest CreateWebCameraTestManifest(string friendlyName, int width, int height, int frameRateNumerator, int frameRateDenominator)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();
            int exposure = -5;
            int focus = 10;

            IProducer<RGBCameraAgentMessage> rgbCameraAgent = VisionManifest.AddRGBCameraAgent(
                                                                manifest,
                                                                views,
                                                                frameRateNumerator,
                                                                frameRateDenominator,
                                                                width,
                                                                height,
                                                                friendlyName,
                                                                "rgbA",
                                                                exposure,
                                                                focus);

            // add the UI
            UserInterfaceAgent interfaceAgent = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(interfaceAgent);

            // specify which agents to activate
            manifest.ActiveAgents.Add(rgbCameraAgent.Name);
            manifest.ActiveAgents.Add("UserInterface");

            return manifest;
        }

        /// <summary>
        /// Creates a manifest for recording RGB data
        /// </summary>
        /// <param name="frameRateNumerator">Frame Rate Numerator</param>
        /// <param name="frameRateDenominator">Frame Rate Denominator</param>
        /// <returns>The new manifest</returns>
        public static Manifest CreateRGBCameraRecorderManifest(int frameRateNumerator = 15, int frameRateDenominator = 1)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            // add the agents
            IProducer<RGBCameraAgentMessage> rgbCamera = VisionManifest.AddRGBCameraAgent(manifest, views, frameRateNumerator, frameRateDenominator);

            // add the recorders
            FileRecorderProxy rgbCameraRecorder = VisionManifest.AddRGBCameraRecorder(manifest, rgbCamera);

            // add the UI
            UserInterfaceAgent interfaceAgent = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(interfaceAgent);

            // specify which agents to activate
            manifest.ActiveAgents.Add(rgbCameraRecorder.Name);
            manifest.ActiveAgents.Add("UserInterface");

            return manifest;
        }

        /// <summary>
        /// Create a manifest to demo simultaneously using a Kinect One and a kinect 360
        /// </summary>
        /// <param name="robotConfigFolder">Calibration Data Folder</param>
        /// <returns>A manifest that enables kinect RGB depth + RGB and its visualizations</returns>
        public static Manifest CreateKinectV2DemoManifest(string robotConfigFolder)
        {
            Manifest manifest = new Manifest();
            ////string kinectV2Address = "mrevow7";
            ////List<ViewDefinition> views = new List<ViewDefinition>();

            ////ProxyDefinition kinectProcessorV2 = ProxyDefinition.CreateRemoteProxy(
            ////                name: "kinectV2Processor",
            ////                address: kinectV2Address,
            ////                publishContracts: new[] { typeof(DepthCameraAgentMessage<KinectDepthCameraParameters>) },
            ////                publishNetworkPortNumber: (int)RemotingPorts.KinectRawPublish,
            ////                controlContracts: null,
            ////                controlNetworkPortNumber: 0);

            ////manifest.RemoteAgentProxies.Add(kinectProcessorV2);

            ////DepthCameraModelAgent modelDepth = new DepthCameraModelAgent(
            ////name: "depthModel",
            ////producer: kinectProcessorV2.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>(),
            ////maxUpdateIntervalMilliseconds: 100,
            ////disparityFactor: 2,
            ////imageFormat: SimplePixelFormat.Rgb24,
            ////source: DepthCameraModelAgent.KinectVisualizationSourceEnum.DepthImageSource);
            ////manifest.Agents.Add(modelDepth);

            ////ViewDefinition def = new ViewDefinition("V2 Depth View", typeof(DepthCameraView), modelDepth);
            ////views.Add(def);

            ////DepthCameraModelAgent modelIR = new DepthCameraModelAgent(
            ////name: "IR model",
            ////producer: kinectProcessorV2.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>(),
            ////maxUpdateIntervalMilliseconds: 100,
            ////disparityFactor: 2,
            ////imageFormat: SimplePixelFormat.Rgb24,
            ////source: DepthCameraModelAgent.KinectVisualizationSourceEnum.IRimageSource);
            ////manifest.Agents.Add(modelIR);

            ////def = new ViewDefinition("V2 IR View", typeof(DepthCameraView), modelIR);
            ////views.Add(def);

            ////DepthCameraModelAgent modelMask = new DepthCameraModelAgent(
            ////    name: "V2 IR masked",
            ////    producer: kinectProcessorV2.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>(),
            ////    maxUpdateIntervalMilliseconds: 100,
            ////    disparityFactor: 2,
            ////    imageFormat: SimplePixelFormat.Rgb24,
            ////    source: DepthCameraModelAgent.KinectVisualizationSourceEnum.IRImageMaskedByDepthSource,
            ////    minMask: 680,
            ////    maxMask: 750);
            ////manifest.Agents.Add(modelMask);

            ////def = new ViewDefinition("V2 IR Masked", typeof(DepthCameraView), modelMask);
            ////views.Add(def);

            ////DepthCameraModelAgent modelV2RGB = new DepthCameraModelAgent(
            ////    name: "V2 RGB",
            ////    producer: kinectProcessorV2.AsProducer<DepthCameraAgentMessage<KinectDepthCameraParameters>>(),
            ////    maxUpdateIntervalMilliseconds: 100,
            ////    disparityFactor: 2,
            ////    imageFormat: SimplePixelFormat.Rgb24,
            ////    source: DepthCameraModelAgent.KinectVisualizationSourceEnum.ColorImageSource,
            ////    minMask: 680,
            ////    maxMask: 750);
            ////manifest.Agents.Add(modelV2RGB);
            ////def = new ViewDefinition("V2 RGB", typeof(DepthCameraView), modelV2RGB);
            ////views.Add(def);

            ////KinectAgent kinect = VisionManifest.AddKinectPlusRGB(manifest, views);
            ////KinectAgentMessageProcessor processor360 = VisionManifest.AddKinectProcessor(manifest, views, kinect, robotConfigFolder);

            ////KinectRGBCameraModelAgent modelRGB = new KinectRGBCameraModelAgent(
            ////    name: "VisualCameraModelAgentRGB",
            ////    producer: processor360,
            ////    maxUpdateIntervalMilliseconds: 1,
            ////    enableDepthMasking: false,
            ////    minDepthMaskingRange: 3000,
            ////    maxDepthMaskingRange: 3200,
            ////    imageFormat: SimplePixelFormat.Rgb24);

            ////manifest.Agents.Add(modelRGB);

            ////ViewDefinition defRGB = new ViewDefinition("360 RGB", typeof(DepthCameraView), modelRGB);
            ////views.Add(defRGB);

            ////// add the UI
            ////UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            ////manifest.Agents.Add(userInterface);

            ////// specify the leaf agents to activate
            ////manifest.ActiveAgents.Add(userInterface.Name);
            ////manifest.ActiveAgents.Add(processor360.Name);
            ////manifest.ActiveAgents.Add(kinectProcessorV2.Name);

            return manifest;
        }

        /// <summary>
        /// Create a manifest to capture RGB from webcam
        /// </summary>
        /// <returns>A manifest that enables webcam RGB and its visualizations</returns>
        public static Manifest CreateWebcamRGBPlaybackManifest()
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            // add the agents
            IProducer<RGBCameraAgentMessage> rgbCameraAgent = AddRGBCameraAgent(manifest, views);

            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);
            manifest.ActiveAgents.Add(userInterface.Name);
            manifest.ActiveAgents.Add(rgbCameraAgent.Name);

            return manifest;
        }

        /// <summary>
        /// Create a manifest to estimate the camera pose from RGB images
        /// </summary>
        /// <param name="robotConfigFolder">Calibration Folder</param>
        /// <param name="environmentConfigFolder">Environment config folder.</param>
        /// <returns>A manifest that extracts ground truth estimate from RGB images</returns>
        public static Manifest CreateCameraPoseEstimationManifest(string robotConfigFolder, string environmentConfigFolder)
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            // add the agents             
            IProducer<RGBCameraAgentMessage> rgbCameraAgent = AddRGBCameraAgent(manifest, views);

            IProducer<CameraPoseEstimationAgentMessage> groundTruthExtractionAgent = AddCameraPoseEstimationAgent(rgbCameraAgent, manifest, views, robotConfigFolder, environmentConfigFolder);

            // add the UI
            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);
            manifest.ActiveAgents.Add(userInterface.Name);

            // specify the leaf agents to activate
            manifest.ActiveAgents.Add(userInterface.Name);
            manifest.ActiveAgents.Add(rgbCameraAgent.Name);
            manifest.ActiveAgents.Add(groundTruthExtractionAgent.Name);

            return manifest;
        }

        /// <summary>
        /// Add object detection components to a manifest
        /// </summary>
        /// <param name="robotConfigFolder">Calibration Data Folder</param>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="views">The set of views to add to</param>
        /// <param name="kinectV2Address">Address of kinect V2 proxy</param>
        /// <param name="detectorConfigFiles">Array of object detection configuration file paths</param>
        /// <returns>Returns the object detector agent</returns>
        public static KinectObjectDetectorIRAgent CreateObjectDetectionManifest(string robotConfigFolder, Manifest manifest, List<ViewDefinition> views, string kinectV2Address, string[] detectorConfigFiles)
        {
            string kinectAgentName = "KinectAgentRaw";

            ProxyDefinition kinectProcessor = ProxyDefinition.CreateRemoteProxy(
                                        name: kinectAgentName,
                                        address: kinectV2Address,
                                        publishContracts: new[] { typeof(DepthIRColorCameraMessage<DepthParameters, IRParameters, ColorParameters>) },
                                        publishNetworkPortNumber: (int)RemotingPorts.KinectRawPublish,
                                        controlContracts: null,
                                        controlNetworkPortNumber: 0);

            manifest.RemoteAgentProxies.Add(kinectProcessor);

            KinectObjectDetectorIRAgent objectDetector = new KinectObjectDetectorIRAgent(
                                        name: "objectDetector",
                                        producer: kinectProcessor.AsProducer<DepthIRColorCameraMessage<DepthParameters, IRParameters, ColorParameters>>(),
                                        detectorConfigurationFiles: detectorConfigFiles);
            manifest.Agents.Add(objectDetector);

            ObjectDetectionModelAgent objectDetectorModel = new ObjectDetectionModelAgent(
                    name: "IRDetectCameraModel",
                    producer: objectDetector,
                    maxUpdateIntervalMilliseconds: 1);

            manifest.Agents.Add(objectDetectorModel);

            ViewDefinition def = new ViewDefinition("IR View", typeof(Microsoft.Robotics.Vision.Visualization.Cameras.ObjectDetection.ObjectDetectionView), objectDetectorModel);
            views.Add(def);

            manifest.ActiveAgents.Add(kinectProcessor.Name);
            manifest.ActiveAgents.Add(objectDetector.Name);
            manifest.ActiveAgents.Add(objectDetectorModel.Name);

            return objectDetector;
        }

                /// <summary>
        /// Creates a manifest that estimates the pose of a kinect $v1$ camera
        /// </summary>
        /// <returns>A manifest</returns>
        public static Manifest CreateKinectPoseEstimatorManifest()
        {
            Manifest manifest = new Manifest();
            List<ViewDefinition> views = new List<ViewDefinition>();

            KinectAgent kinectAgent = VisionManifest.AddKinect(
                manifest: manifest, 
                views: views,
                colorImageFormat: ColorImageFormat.RgbResolution1280x960Fps12);

            CameraPoseEstimatorAgent<KinectColorCameraParameters> poseAgent = new CameraPoseEstimatorAgent<KinectColorCameraParameters>("Camera Pose Estimator", kinectAgent);

            manifest.Agents.Add(poseAgent);
            manifest.ActiveAgents.Add(poseAgent.Name);

            // add the UI
            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);

            // specify the leaf agents to activate
            manifest.ActiveAgents.Add(userInterface.Name);

            return manifest;
        }

        /// <summary>
        /// Adds an RGB camera agent.
        /// </summary>
        /// <param name="manifest">Manifest to which the agent is to be added.</param>
        /// <param name="views">The list to add views to.</param>
        /// <param name="frameRateNumerator">Frame rate numerator in frames.</param>
        /// <param name="frameRateDenominator">Frame rate denominator in seconds.</param>
        /// <param name="width">Desired picture width</param>
        /// <param name="height">Desired picture height</param>
        /// <param name="friendlyName">Web camera friendly name</param>
        /// <param name="cameraName">Optional camera agent name</param>
        /// <param name="cameraExposureLevel">RGB Camera exposure level. This was set to -10, but due to recent windows update (10/15/2013) where -10 is too dark it was changed to -5.</param>
        /// <param name="cameraFocus">Camera focus level, default is 0 which is automatic focus</param>
        /// <returns>RGB camera agent.</returns>
        public static RGBCameraAgent AddRGBCameraAgent(
            Manifest manifest,
            List<ViewDefinition> views,
            int frameRateNumerator = 15,
            int frameRateDenominator = 1,
            int width = 1280,
            int height = 720,
            string friendlyName = @"[\w\s]*LifeCam Cinema[.]*",
            string cameraName = "RGBCamera",
            int cameraExposureLevel = 0,
            int cameraFocus = 0)
        {
            RGBCameraProperty[] defaultCameraProperties = new RGBCameraProperty[] 
                    {
                    new RGBCameraProperty(ManagedCameraControlProperty.Exposure, cameraExposureLevel, ManagedCameraControlPropertyFlags.Auto),
                    new RGBCameraProperty(ManagedCameraControlProperty.Focus, cameraFocus, ManagedCameraControlPropertyFlags.Manual)
                    };

            return AddRGBCameraAgentExt(manifest, views, frameRateNumerator, frameRateDenominator, width, height, friendlyName, cameraName, defaultCameraProperties);
        }

        /// <summary>
        /// Adds an RGB camera agent, extended version. This version gives finer control over all the camera properties as compared
        /// to the original legacy version.
        /// </summary>
        /// <param name="manifest">Manifest to which the agent is to be added.</param>
        /// <param name="views">The list to add views to.</param>
        /// <param name="frameRateNumerator">Frame rate numerator in frames.</param>
        /// <param name="frameRateDenominator">Frame rate denominator in seconds.</param>
        /// <param name="width">Desired picture width</param>
        /// <param name="height">Desired picture height</param>
        /// <param name="friendlyName">Web camera friendly name</param>
        /// <param name="cameraName">Optional camera agent name</param>
        /// <param name="cameraProperties">Optional list of camera properties set during initialization</param>
        /// <param name="videoProperties">Optional list of video properties set during initialization</param>
        /// <param name="cameraPose">Optional camera pose</param>
        /// <param name="interestPatch">Optional patch of interest for images from camera</param>
        /// <param name="symLink">Optional symbolic link for the camera. Can be null</param>
        /// <param name="propertyResetIntervalMsec">Property reset interval in mSec. 0 means never reset</param>
        /// <returns>RGB camera agent.</returns>
        public static RGBCameraAgent AddRGBCameraAgentExt(
            Manifest manifest, 
            List<ViewDefinition> views, 
            int frameRateNumerator = 15, 
            int frameRateDenominator = 1,
            int width = 1280,
            int height = 720,
            string friendlyName = @"[\w\s]*LifeCam[.]*",
            string cameraName = "RGBCamera",
            RGBCameraProperty[] cameraProperties = null,
            RGBVideoProperty[] videoProperties = null,
            Pose? cameraPose = null,
            Rectangle? interestPatch = null,
            string symLink = null,
            long propertyResetIntervalMsec = 0)
        {
            Pose pose = cameraPose.HasValue ? cameraPose.Value : new Pose();
            Rectangle patch = interestPatch.HasValue ? interestPatch.Value : new Rectangle();

            RGBCameraAgent rgbCameraAgent = new RGBCameraAgent(
                    name: cameraName,
                    width: width,
                    height: height,
                    frameRateNumerator: frameRateNumerator,
                    frameRateDenominator: frameRateDenominator,
                    subType: "YUY2",
                    friendlyName: friendlyName,
                    cameraProperties: cameraProperties,
                    cameraPose: pose,
                    interestPatch: patch,
                    cameraSymbolicLink: symLink,
                    videoProperties: videoProperties,
                    propertyResetIntervalMsec: propertyResetIntervalMsec);

            manifest.Agents.Add(rgbCameraAgent);

            WebcamRGBCameraModelAgent modelRGB = new WebcamRGBCameraModelAgent(
                name: "VisualCameraModelAgentRGB_" + cameraName,
                producer: rgbCameraAgent,
                maxUpdateIntervalMilliseconds: 16,
                imageFormat: SimplePixelFormat.Rgb24);
            manifest.Agents.Add(modelRGB);

            ViewDefinition def = new ViewDefinition("Webcam_" + cameraName, typeof(WebCameraView), modelRGB);
            views.Add(def);

            return rgbCameraAgent;
        }

        /// <summary>
        /// Adds a desktop proxy for control of an RGB camera agent running on the robot.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="robotNameOrAddress">The name or IP address of the robot</param>
        /// <returns>The proxy</returns>
        public static IControllable<RGBCameraInputMessage> RGBCameraControlRemoteProxy(Manifest manifest, string robotNameOrAddress)
        {
            // now create a proxy with the same name and add it to the manifest
            ProxyDefinition rgbCameraControlProxy = ProxyDefinition.CreateRemoteProxy(
                name: "RGBCameraControl",
                address: robotNameOrAddress,
                publishContracts: null,
                publishNetworkPortNumber: 0,
                controlContracts: new[] { typeof(RGBCameraInputMessage) },
                controlNetworkPortNumber: (int)RemotingPorts.RgbCameraControl);
            manifest.RemoteAgentProxies.Add(rgbCameraControlProxy);

            return rgbCameraControlProxy.AsControllable<RGBCameraInputMessage>();
        }

        /// <summary>
        /// Adds a camera pose estimation agent.
        /// </summary>
        /// <param name="rgbCameraAgent">RGB camera agent which provides the images for the camera pose estimation agent</param>
        /// <param name="manifest">Manifest to which the agent has to be added</param>
        /// <param name="views">The list to add views to.</param>
        /// <param name="ev4CalibrationFolder">Calibration folder.</param>
        /// <param name="environmentConfigFolder">Environment calibration folder</param>
        /// <param name="addUI">Whether to add UI corresponding to the agent.</param>
        /// <returns>Ground truth extraction agent.</returns>
        public static IProducer<CameraPoseEstimationAgentMessage> AddCameraPoseEstimationAgent(
            IProducer<RGBCameraAgentMessage> rgbCameraAgent,
            Manifest manifest,
            List<ViewDefinition> views,
            string ev4CalibrationFolder,
            string environmentConfigFolder,
            bool addUI = true)
        {
            CameraPoseEstimationAgent cameraPoseEstimationAgent = new CameraPoseEstimationAgent(
                name: "CameraPoseEstimationAgent",
                producer: rgbCameraAgent, 
                robotConfigFolder: ev4CalibrationFolder,
                environmentConfigFolder: environmentConfigFolder);
            manifest.Agents.Add(cameraPoseEstimationAgent);

            if (addUI)
            {
                CameraPoseEstimationModelAgent cameraPoseEstimationModel = new CameraPoseEstimationModelAgent(
                    name: "GroundTruthExtractionModelAgentRGB",
                    rgbImageProducer: rgbCameraAgent,
                    cameraPoseEstimationProducer: cameraPoseEstimationAgent,
                    maxUpdateIntervalMilliseconds: 16,
                    imageFormat: SimplePixelFormat.Gray8);
                manifest.Agents.Add(cameraPoseEstimationModel);

                ViewDefinition def = new ViewDefinition("Camera Pose Estimation View", typeof(CameraPoseEstimationView), cameraPoseEstimationModel);
                views.Add(def);
            }

            return cameraPoseEstimationAgent;
        }

        /// <summary>
        /// Adds a remoting proxy to broadcast camera pose estimation agent messages over the network.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="cameraPoseEstimation">The agent to network-enable.</param>
        /// <param name="desktopNameOrAddress">The name or IP address of the machine to broadcast to (e.g. a desktop machine)</param>
        /// <returns>A remoting proxy</returns>
        public static ProxyDefinition AddCameraPoseEstimationNetworkEnabledProxy(Manifest manifest, IProducer<CameraPoseEstimationAgentMessage> cameraPoseEstimation, string desktopNameOrAddress)
        {
            ProxyDefinition cameraPoseEstimationProxy = ProxyDefinition.CreateNetworkEnabledProxy(
                name: cameraPoseEstimation.Name + "Proxy",
                producer: cameraPoseEstimation.Name,
                address: desktopNameOrAddress,
                publishContracts: new[] { typeof(CameraPoseEstimationAgentMessage) },
                publishNetworkPortNumber: (int)RemotingPorts.CameraPoseEstimationPublish,
                controlContracts: new[] { typeof(CameraPoseEstimationInputMessage) },
                controlNetworkPortNumber: (int)RemotingPorts.CameraPoseEstimationControl);
            manifest.NetworkEnabledAgentProxies.Add(cameraPoseEstimationProxy);

            return cameraPoseEstimationProxy;
        }

        /// <summary>
        /// Adds a desktop proxy for a camera pose estimation agent running on the robot.
        /// </summary>
        /// <param name="manifest">The manifest to add to.</param>
        /// <param name="robotNameOrAddress">The name or IP address of the robot</param>
        /// <returns>The proxy</returns>
        public static IProducer<CameraPoseEstimationAgentMessage> AddCameraPoseEstimationRemoteProxy(Manifest manifest, string robotNameOrAddress)
        {
            // now create a proxy with the same name and add it to the manifest
            ProxyDefinition cameraPoseEstimationProxy = ProxyDefinition.CreateRemoteProxy(
                name: "CameraPoseEstimationAgent",
                address: robotNameOrAddress,
                publishContracts: new[] { typeof(CameraPoseEstimationAgentMessage) },
                publishNetworkPortNumber: (int)RemotingPorts.CameraPoseEstimationPublish,
                controlContracts: new[] { typeof(CameraPoseEstimationInputMessage) },
                controlNetworkPortNumber: (int)RemotingPorts.CameraPoseEstimationControl);
            manifest.RemoteAgentProxies.Add(cameraPoseEstimationProxy);

            return cameraPoseEstimationProxy.AsProducer<CameraPoseEstimationAgentMessage>();
        }

        /// <summary>
        /// Creates a generic RGB camera playback manifest.
        /// </summary>
        /// <param name="manifest">Manifest for generic RGB playback</param>                
        /// <param name="rgbCameraAgent">RGB Camera Agent for the specific device</param>
        private static void AddGenericRGBCameraView(Manifest manifest, IProducer<RGBCameraAgentMessage> rgbCameraAgent)
        {
            List<ViewDefinition> views = new List<ViewDefinition>();

            WebcamRGBCameraModelAgent modelRGB = new WebcamRGBCameraModelAgent(
            name: "VisualCameraModelAgentRGB",
            producer: rgbCameraAgent,
            maxUpdateIntervalMilliseconds: 16,
            imageFormat: SimplePixelFormat.Rgb24);

            manifest.Agents.Add(modelRGB);

            ViewDefinition def = new ViewDefinition("Webcam View", typeof(WebCameraView), modelRGB);
            views.Add(def);

            // add the UI
            UserInterfaceAgent userInterface = new UserInterfaceAgent("UserInterface", views.ToArray());
            manifest.Agents.Add(userInterface);

            // specify the leaf agents to activate
            manifest.ActiveAgents.Add(userInterface.Name);
        }
    }
}
